home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 February: Tool Chest / Dev.CD Feb 94.toast / New System Software Extensions / QuickDraw™ GX v1.0ß2 / Interfaces & Libraries / graphics libraries / shape controls library.c < prev    next >
Encoding:
Text File  |  1993-09-15  |  44.8 KB  |  1,354 lines  |  [TEXT/MPS ]

  1. /* gxShape controls library:
  2.     library to allow the user to manipulate (gxTransform) a set of shapes
  3.     by Dave Good and Jeff Kreegar
  4.     Copyright 1991 - 1992 Apple Computer, Inc.    All rights reserved.    */
  5.  
  6. //    9/14/93 - dmh - Corrected minor probs for the b2 seed.  Search for "dmh 9-14-93"
  7.  
  8. #include <Memory.h>
  9. #include <Events.h>
  10. #include <OSUtils.h>
  11. #include <ToolUtils.h>
  12.  
  13. #include "graphics debugging.h"
  14. #include "graphics errors.h"
  15. #include "graphics toolbox.h"
  16. #include "graphics libraries.h"
  17. #include "shape controls library.h"
  18.  
  19.  
  20. /*----------------------------------------------------------------------------------------------------*/
  21. /* private constants and structures */
  22. /*----------------------------------------------------------------------------------------------------*/
  23.  
  24.  
  25. /*** this is not used or completed; however it is a placeholder for the future */
  26. typedef struct portBuffer {
  27.     gxViewGroup   group;
  28. } portBuffer;
  29.  
  30.  
  31. typedef struct shapeControlRecord {
  32.     /* ----- can be accessed in a public way ----- */
  33.     gxShape       items;
  34.     gxShape       foreground;
  35.     gxShape       background;
  36.     gxShape       selection;
  37.     /* ----- private fields follow ----- */
  38.     gxViewPort          **originalPorts;
  39.     gxShape       controls;
  40.     gxPoint           savedCenter;              /* this is either (gxPositiveInfinity, gxPositiveInfinity) or the saved rotate center */
  41.     long            buildHiddenLevel;
  42.     gxRectangle       **invalidAreas;
  43. } shapeControlRecord, *shapeControlPtr;
  44.  
  45.  
  46. #define controlHandleGap        ff(6)        /* this is the distance between the bounds of the gxShape and the bounds of the control handles */
  47. #define minimumObjectSizeH        ff(2)        /* this is the smallest horizontal size that an object may be scaled to */
  48. #define minimumObjectSizeV        ff(2)        /* this is the smallest vertical size that an object may be scaled to */
  49. #define rotateControlRadius     ff(7)
  50. #define rotateCenterSize        ff(2)
  51.  
  52.  
  53. typedef enum {
  54.     /* ----- special commands ----- */
  55.     firstSpecialCommand = -1,
  56.     commandSelect = firstSpecialCommand,
  57.     commandMove,
  58.     lastSpecialCommand = commandMove,
  59.     /* ----- indexes into the shapeControl->controls picture that correspond to different commands ----- */
  60.     firstIndexCommand = 1,
  61.     commandRotate = firstIndexCommand,
  62.     commandMoveRotateCenter,
  63.     commandScale,
  64.     commandSkew = commandScale + 4,
  65.     lastIndexCommand = commandSkew + 3,
  66.     /* ----- modifiers for commandScale follow ----- */
  67.     leftTop = 0,
  68.     rightTop,
  69.     rightBottom,
  70.     leftBottom,
  71.     /* ----- modifiers for commandSkew follow ----- */
  72.     centerTop = 0,
  73.     centerRight,
  74.     centerBottom,
  75.     centerLeft
  76. } shapeControlCommands;
  77.  
  78.  
  79. /*----------------------------------------------------------------------------------------------------*/
  80. /* private generic routines */
  81. /*----------------------------------------------------------------------------------------------------*/
  82.  
  83.  
  84. static fixed FixedAbs(fixed value)
  85. {
  86.     if( value < 0 )
  87.         return -value;
  88.     return value;
  89. }
  90.  
  91. static gxViewPort *DereferenceViewPortList(gxViewPort **list)
  92. {
  93.     HLock((Handle)list);
  94.     return *list;
  95. }
  96.  
  97. static void ReleaseViewPortList(gxViewPort **list)
  98. {
  99.     HUnlock((Handle)list);
  100. }
  101.  
  102. static gxViewPort **NewViewPortListFromShape(gxShape source)
  103. {
  104.     gxViewPort **list = (gxViewPort **)NewHandleClear((GXGetShapeViewPorts(source, nil) + 1) * sizeof(gxViewPort));
  105.  
  106.     IfErrorReturnNil(list == nil, out_of_memory);
  107.     GXGetShapeViewPorts(source, DereferenceViewPortList(list));
  108.     ReleaseViewPortList(list);
  109.     return list;
  110. }
  111.  
  112. static long CountViewPortList(gxViewPort **list)
  113. {
  114.     return (GetHandleSize((Handle)list) / sizeof(gxViewPort)) - 1;
  115. }
  116.  
  117. static void DisposeViewPortList(gxViewPort **list)
  118. {
  119.     NilParamReturn(list);
  120.     DisposeHandle((Handle)list);
  121. }
  122.  
  123. static boolean ShapeInPicture(gxShape test, gxShape picture)
  124. {
  125.     long count;
  126.  
  127.     NilShapeReturnNil(test);
  128.     NilShapeReturnNil(picture);
  129.  
  130.     count = GXGetPicture(picture, nil, nil, nil, nil);
  131.     while( count ) {
  132.         gxShape item;
  133.  
  134.         GXGetPictureParts(picture, count, 1, &item, nil, nil, nil);
  135.         if( item == test )
  136.             return true;
  137.         --count;
  138.     }
  139.     return false;
  140. }
  141.  
  142. static void GetPictureBounds(gxShape picture, gxRectangle *bounds, gxMapping *concatMatrix)
  143. {
  144.     long count = GXGetPicture(picture, nil, nil, nil, nil);
  145.  
  146.     /* if there are no items in the picture, then there is nothing to do */
  147.     if( count == 0 )
  148.         return;
  149.  
  150.     do {    gxShape item;
  151.         gxTransform itemXform;
  152.         gxMapping matrix;
  153.  
  154.         GXGetPictureParts(picture, count, 1, &item, nil, nil, &itemXform);
  155.  
  156.         /* if there was no overriding gxTransform, then use the item’s gxTransform. calculate the concatenated gxMapping */
  157.         if( itemXform == nil )
  158.             itemXform = GXGetShapeTransform(item);
  159.         MapMapping(GXGetTransformMapping(itemXform, &matrix), concatMatrix);
  160.  
  161.         if( GXGetShapeType(item) == gxPictureType ) {
  162.             GetPictureBounds(item, bounds, &matrix);
  163.         } else {
  164.             gxRectangle itemBounds;
  165.             gxMapping tempMatrix;
  166.             gxShape boundsShape = GXNewRectangle( GXGetShapeLocalBounds(item, &itemBounds) );
  167.  
  168.             /* map our bounds gxRectangle through the concatenated gxMapping. note that since GXGetShapeLocalBounds already
  169.             maps the bounds by the item’s gxTransform, we need to undo this effect. */
  170.         #ifdef debugging
  171.             GXIgnoreGraphicsNotice(mapping_unaffected);
  172.         #endif
  173.                 MapMapping(&matrix, InvertMapping(&tempMatrix, GXGetTransformMapping(GXGetShapeTransform(item), &tempMatrix)));
  174.                 GXMapShape(boundsShape, &matrix);
  175.         #ifdef debugging
  176.             GXPopGraphicsNotice();
  177.         #endif
  178.             /* get the bounds of our transformed bounds gxRectangle and then dispose of the temporary bounds gxShape */
  179.             GXGetShapeBounds(boundsShape, 0L, &itemBounds);
  180.             GXDisposeShape(boundsShape);
  181.  
  182.             /* union this item’s bounds into the main picture bounds (work around a problem with GXUnionRectangle) */
  183.             if( bounds->right < bounds->left || bounds->bottom < bounds->top )
  184.                 *bounds = itemBounds;
  185.             else
  186.                 GXUnionRectangle(bounds, bounds, &itemBounds);
  187.         }
  188.     } while( --count );
  189. }
  190.  
  191. static gxRectangle *GetShapeTransformedBounds(gxShape source, gxRectangle *bounds)
  192. {
  193.     gxMapping matrix;
  194.  
  195.     /* since graphics currently can’t get the correct bounds for a picture gxShape, we need special code */
  196.     if( GXGetShapeType(source) == gxPictureType ) {
  197.         bounds->left = bounds->top = gxPositiveInfinity;
  198.         bounds->right = bounds->bottom = gxNegativeInfinity;
  199.         GetPictureBounds( source, bounds, GXGetShapeMapping(source, &matrix) );
  200.         return bounds;
  201.     }
  202.  
  203.     /* otherwise, just use GXGetShapeLocalBounds */
  204.     return GXGetShapeLocalBounds(source, bounds);
  205. }
  206.  
  207. static gxPoint *GetShapeTransformedCenter(gxShape source, gxPoint *center)
  208. {
  209.     gxMapping matrix;
  210.  
  211.     /* since graphics currently can’t calculate the center of picture shapes, we need special code */
  212.     if( GXGetShapeType(source) == gxPictureType ) {
  213.         gxRectangle bounds;
  214.  
  215.         GetShapeTransformedBounds(source, &bounds);
  216.         center->x = (bounds.right >> 1) + (bounds.left >> 1);
  217.         center->y = (bounds.top >> 1) + (bounds.bottom >> 1);
  218.     } else
  219.         MapPoints( GXGetShapeMapping(source, &matrix), 1, GXGetShapeCenter(source, 0L, center) );
  220.     return center;
  221. }
  222.  
  223.  
  224. /*----------------------------------------------------------------------------------------------------*/
  225. /* private internal routines */
  226. /*----------------------------------------------------------------------------------------------------*/
  227.  
  228.  
  229. #if 0
  230. static boolean AllocateOffscreen(shapeControl source)
  231. {
  232.     return false;
  233.     gxBitmap deepestBits;
  234.     gxShape area;
  235.  
  236.     /* this ensures that the first device that we find will be put in deepestBits */
  237.     deepestBits.pixelSize = -1;
  238.  
  239.     area = GXNewShape(gxFullType);
  240.     GXSetShapeViewPorts(area, portCount, ports);
  241.     while( portCount-- ) {
  242.         gxViewDevice devices[maximumViewThings];
  243.         long deviceCount;
  244.  
  245.         if( (deviceCount = GXGetShapeGlobalViewDevices(area, ports[portCount], devices)) > maximumViewThings )
  246.             DebugStr("\pexceded the maximum number of viewDevices for a gxShape control");
  247.         while( deviceCount-- ) {
  248.             gxShape bitsShape;
  249.             gxBitmap bits;
  250.  
  251.             bitsShape = GXGetViewDeviceBitmap(devices[deviceCount]);
  252.             GXGetBitmap(bitsShape, &bits, nil);
  253.             if( bits.pixelSize > deepestBits.pixelSize )
  254.                 deepestBits = bits;
  255.             GXDisposeShape(bitsShape);
  256.         }
  257.     }
  258.     GXDisposeShape(area);
  259.  
  260.     if( deepestBits.pixelSize > 0 ) {
  261.         gxShape bitsShape;
  262.  
  263.         deepestBits.image = nil;    /* make graphics allocate our offscreen bit image */
  264.         bitsShape = GXNewBitmap(&deepestBits, nil);
  265.         CreateOffscreen(&source->buffer, bitsShape);
  266.         GXDisposeShape(bitsShape);
  267.     } else
  268.         FillBytes(&source->buffer, sizeof(source->buffer), 0);
  269.  
  270.     /*** needs work */
  271. }
  272. #endif
  273.  
  274. static void InvalidateRectangle(shapeControlPtr targetPtr, gxRectangle *newArea)
  275. {
  276.     if( targetPtr->invalidAreas ) {
  277.         gxRectangle *listPtr = *targetPtr->invalidAreas;
  278.         gxRectangle *maxPtr = (gxRectangle *)((char *)listPtr + GetHandleSize((Handle)targetPtr->invalidAreas));
  279.  
  280.         while( listPtr != maxPtr &&
  281.             (listPtr->top < newArea->top || listPtr->top == newArea->top && listPtr->left < newArea->left) ) {
  282.             ++listPtr;
  283.         }
  284.         Munger((Handle)targetPtr->invalidAreas, (char *)listPtr - (char *)*targetPtr->invalidAreas, nil, 0,
  285.             (Ptr)newArea, sizeof(gxRectangle));
  286.     } else {
  287.         targetPtr->invalidAreas = (gxRectangle **)NewHandle(sizeof(gxRectangle));
  288.         **targetPtr->invalidAreas = *newArea;
  289.  
  290.         /*** reduce the gxRectangle list here */
  291.     }
  292.  
  293. #if 0  /* re-enable this to show which areas will be updated */
  294.     {    gxShape invalidShape = GXNewRectangle(newArea);
  295.     #ifdef debugging
  296.         GXIgnoreGraphicsNotice(color_already_set);
  297.     #endif
  298.             SetShapeCommonColor(invalidShape, gxBlack);
  299.     #ifdef debugging
  300.         GXPopGraphicsNotice();
  301.     #endif
  302.         GXDrawShape(invalidShape);
  303.         GXDisposeShape(invalidShape);
  304.     }
  305. #endif
  306. }
  307.  
  308. static void InvalidatePicture(shapeControlPtr targetPtr, gxShape control)
  309. {
  310.     /*** later, we need to fix the picture code below and then only invalidate the bounds of the sub-items */
  311.     if( false && GXGetShapeType(control) == gxPictureType ) {
  312.         long count = GXGetPicture(control, nil, nil, nil, nil);
  313.         while( count > 0 ) {
  314.             gxShape item;
  315.             gxTransform itemXform, originalXform;
  316.             gxRectangle bounds;
  317.  
  318.             GXGetPictureParts(control, count, 1, &item, nil, nil, &itemXform);
  319.             if( itemXform ) {
  320.                 originalXform = GXGetShapeTransform(item);
  321.                 GXSetShapeTransform(item, itemXform);
  322.             }
  323.             InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  324.             if( itemXform )
  325.                 GXSetShapeTransform(item, originalXform);
  326.             --count;
  327.         }
  328.     } else {
  329.         gxRectangle bounds;
  330.         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(control, &bounds));
  331.     }
  332. }
  333.  
  334. static void RedrawShapeControl(shapeControlPtr targetPtr)
  335. {
  336.     if( targetPtr->invalidAreas ) {
  337.         gxRectangle *listPtr, *maxPtr;
  338.  
  339.         /* lock down our handle of rectangular invalid areas */
  340.         HLock((Handle)targetPtr->invalidAreas);
  341.         listPtr = *targetPtr->invalidAreas;
  342.         maxPtr = (gxRectangle *)((char *)listPtr + GetHandleSize((Handle)targetPtr->invalidAreas));
  343.  
  344.         do {    gxShape clip;
  345.             gxViewPort *portList, *childList;
  346.             gxViewPort **childPorts;
  347.  
  348.             /* make a gxViewPort list for all of our child ports (these will hold our invalid clip) */
  349.             childPorts = (gxViewPort **)NewHandleClear( GetHandleSize((Handle)targetPtr->originalPorts) );
  350.             childList = DereferenceViewPortList(childPorts);
  351.  
  352.             /* clip all of the viewPorts that we are drawing into to our current invalid gxRectangle */
  353.             clip = GXNewRectangle(listPtr);
  354.             portList = DereferenceViewPortList(targetPtr->originalPorts);
  355.             while( *portList ) {
  356.                 *childList = GXNewViewPort(GXGetViewPortViewGroup(*portList));
  357.                 GXSetViewPortParent(*childList, *portList);
  358.                 GXSetViewPortClip(*childList, clip);
  359.                 ++portList;
  360.                 ++childList;
  361.             }
  362.             ReleaseViewPortList(targetPtr->originalPorts);
  363.             GXDisposeShape(clip);
  364.  
  365.             /* re-direct all of our drawing into the child viewPorts */
  366.         #ifdef debugging    
  367.             GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  368.         #endif
  369.             {    long portCount = CountViewPortList(childPorts);
  370.  
  371.                 childList = *childPorts;
  372.                 GXSetShapeViewPorts(targetPtr->background, portCount, childList);
  373.                 if( targetPtr->foreground )
  374.                     GXSetShapeViewPorts(targetPtr->foreground, portCount, childList);
  375.                 if( targetPtr->controls )
  376.                     GXSetShapeViewPorts(targetPtr->controls, portCount, childList);
  377.             }
  378.         #ifdef debugging    
  379.             GXPopGraphicsNotice();
  380.         #endif
  381.  
  382.             GXDrawShape(targetPtr->background);
  383.  
  384.             /* draw all the items in the picture, one at a time in the proper order using our clip */
  385.         #if 1
  386.             {    long count = GXGetPicture(targetPtr->items, nil, nil, nil, nil);
  387.                 long index;
  388.  
  389.                 for(index = 1; index <= count; ++index ) {
  390.                     gxViewPort **itemList;
  391.                     gxShape item;
  392.  
  393.                     GXGetPictureParts(targetPtr->items, index, 1, &item, nil, nil, nil);
  394.  
  395.                     /* save the item’s gxViewPort list and re-direct the item into our childPorts */
  396.                     itemList = NewViewPortListFromShape(item);
  397.                     GXSetShapeViewPorts(item, CountViewPortList(childPorts), *childPorts);
  398.  
  399.                     GXDrawShape(item);
  400.  
  401.                     /* restore the item’s gxViewPort list and throw away our saved list memory */
  402.                     DereferenceViewPortList(itemList);
  403.                     GXSetShapeViewPorts(item, CountViewPortList(itemList), *itemList);
  404.                     DisposeViewPortList(itemList);
  405.                 }
  406.             }
  407.         #else
  408.             GXDrawShape(targetPtr->items);
  409.         #endif
  410.  
  411.             if( targetPtr->foreground )
  412.                 GXDrawShape(targetPtr->foreground);
  413.             if( targetPtr->controls )
  414.                 GXDrawShape(targetPtr->controls);
  415.  
  416.             /* re-direct all of our drawing back into the main viewPorts */
  417.         #ifdef debugging
  418.             GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  419.         #endif
  420.             {
  421.                 long portCount = CountViewPortList(targetPtr->originalPorts);
  422.  
  423.                 portList = DereferenceViewPortList(targetPtr->originalPorts);
  424.                 GXSetShapeViewPorts(targetPtr->background, portCount, portList);
  425.                 if( targetPtr->foreground )
  426.                     GXSetShapeViewPorts(targetPtr->foreground, portCount, portList);
  427.                 if( targetPtr->controls )
  428.                     GXSetShapeViewPorts(targetPtr->controls, portCount, portList);
  429.                 ReleaseViewPortList(targetPtr->originalPorts);
  430.             }
  431.         #ifdef debugging
  432.             GXPopGraphicsNotice();
  433.         #endif
  434.             /* dispose of all the viewPorts in the child list and then dispose of the child list itself */
  435.             childList = DereferenceViewPortList(childPorts);
  436.             while( *childList )
  437.                 GXDisposeViewPort(*childList++);
  438.             ReleaseViewPortList(childPorts);
  439.             DisposeViewPortList(childPorts);
  440.         } while( ++listPtr < maxPtr );
  441.  
  442.         /* throw away all the invalid areas since we have just updated them */
  443.         DisposeHandle((Handle)targetPtr->invalidAreas);
  444.         targetPtr->invalidAreas = nil;
  445.     }
  446. }
  447.  
  448. static void BuildSelectionControls(shapeControlPtr targetPtr)
  449. {
  450.     gxRectangle bounds, controlBounds;
  451.     gxShape controls, workShape;
  452.  
  453.     /* don’t re-build anything if building is hidden */
  454.     if( targetPtr->buildHiddenLevel )
  455.         return;
  456.  
  457.     /* throw away the old controls and see if there is a selection; if not, then we are done */
  458.     DisposeShapeAt(&targetPtr->controls);
  459.     if( targetPtr->selection == nil )
  460.         return;
  461.  
  462.     /* create a new controls picture in all of our viewPorts */
  463.     controls = targetPtr->controls = GXNewShape(gxPictureType);
  464.     { gxViewPort *portList = DereferenceViewPortList(targetPtr->originalPorts);
  465.     GXSetShapeViewPorts(targetPtr->controls, CountViewPortList(targetPtr->originalPorts), portList);
  466.     ReleaseViewPortList(targetPtr->originalPorts); }
  467.  
  468.     /* get the bounds of the entire selection and calculate the controlBounds */
  469.     GetShapeTransformedBounds(targetPtr->selection, &bounds);
  470.     IfDebug(bounds.right < bounds.left || bounds.bottom < bounds.top, "\pthe selection should always have a bounds here");
  471.     controlBounds.left = bounds.left - controlHandleGap;
  472.     controlBounds.top = bounds.top - controlHandleGap;
  473.     controlBounds.right = bounds.right + controlHandleGap;
  474.     controlBounds.bottom = bounds.bottom + controlHandleGap;
  475.  
  476.     /* create the scale controls (these are simply rotated images of each other) */
  477.     {    static long scalePoly[] =    { 1, 4,
  478.                                 ff(0), ff(0),
  479.                                 ff(5) + 0x5000, ff(0),
  480.                                 ff(0), ff(5) + 0x5000,
  481.                                 ff(0), ff(0)
  482.                             };
  483.         gxShape tempShape;
  484.  
  485.         workShape = GXNewPolygons((gxPolygons *)scalePoly);
  486.         GXSetShapeFill(workShape, gxWindingFill);
  487.     #ifdef debugging
  488.         GXIgnoreGraphicsNotice(color_already_set);
  489.     #endif
  490.             SetShapeCommonColor(workShape, gxBlack);
  491.     #ifdef debugging
  492.         GXPopGraphicsNotice();
  493.     #endif
  494.         tempShape = GXCopyToShape(nil, workShape);
  495.         GXMoveShapeTo(tempShape, controlBounds.left, controlBounds.top);
  496.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  497.         GXDisposeShape(tempShape);
  498.  
  499.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  500.         tempShape = GXCopyToShape(nil, workShape);
  501.         GXMoveShapeTo(tempShape, controlBounds.right, controlBounds.top);
  502.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  503.         GXDisposeShape(tempShape);
  504.  
  505.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  506.         tempShape = GXCopyToShape(nil, workShape);
  507.         GXMoveShapeTo(tempShape, controlBounds.right, controlBounds.bottom);
  508.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  509.         GXDisposeShape(tempShape);
  510.  
  511.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  512.         tempShape = GXCopyToShape(nil, workShape);
  513.         GXMoveShapeTo(tempShape, controlBounds.left, controlBounds.bottom);
  514.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  515.         GXDisposeShape(tempShape);
  516.  
  517.         GXDisposeShape(workShape);
  518.     }
  519.  
  520.     /* create the skew controls in (top, right, bottom, left) order */
  521.     {    static long skewPoly[] =    { 1, 5,
  522.                                 -ff(3), ff(0),
  523.                                 ff(0), -ff(3),
  524.                                 ff(3), ff(0),
  525.                                 ff(0), ff(3),
  526.                                 -ff(3), ff(0)
  527.                             };
  528.         gxShape tempShape;
  529.         gxPoint center;
  530.  
  531.         center.x = (bounds.right >> 1) + (bounds.left >> 1);
  532.         center.y = (bounds.top >> 1) + (bounds.bottom >> 1);
  533.  
  534.         workShape = GXNewPolygons((gxPolygons *)skewPoly);
  535.         GXSetShapeFill(workShape, gxWindingFill);
  536.     #ifdef debugging
  537.         GXIgnoreGraphicsNotice(color_already_set);
  538.     #endif
  539.             SetShapeCommonColor(workShape, gxBlack);
  540.     #ifdef debugging
  541.         GXPopGraphicsNotice();
  542.     #endif
  543.  
  544.         tempShape = GXCopyToShape(nil, workShape);
  545.         GXMoveShape(tempShape, center.x, controlBounds.top);
  546.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  547.         GXDisposeShape(tempShape);
  548.  
  549.         tempShape = GXCopyToShape(nil, workShape);
  550.         GXMoveShape(tempShape, controlBounds.right + ff(1), center.y);
  551.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  552.         GXDisposeShape(tempShape);
  553.  
  554.         tempShape = GXCopyToShape(nil, workShape);
  555.         GXMoveShape(tempShape, center.x, controlBounds.bottom + ff(3));
  556.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  557.         GXDisposeShape(tempShape);
  558.  
  559.         tempShape = GXCopyToShape(nil, workShape);
  560.         GXMoveShape(tempShape, controlBounds.left, center.y);
  561.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  562.         GXDisposeShape(tempShape);
  563.  
  564.         GXDisposeShape(workShape);
  565.     }
  566.  
  567.     /* create the rotate control and its center gxPoint */
  568.     {    gxRectangle tempRect;
  569.         gxPoint center;
  570.  
  571.         /* get the center of the selection (or the individual gxShape) for the rotate control and its center gxPoint */
  572.         if( targetPtr->savedCenter.x != gxPositiveInfinity ) {
  573.             center = targetPtr->savedCenter;
  574.         } else if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 1 ) {
  575.             gxShape item;
  576.  
  577.             GXGetPicture(targetPtr->selection, &item, nil, nil, nil);
  578.             GetShapeTransformedCenter(item, ¢er);
  579.         } else {
  580.             center.x = (bounds.right >> 1) + (bounds.left >> 1);
  581.             center.y = (bounds.top >> 1) + (bounds.bottom >> 1);
  582.         }
  583.  
  584.         /* create the rotate control */
  585.         tempRect.left        = center.x - rotateControlRadius;
  586.         tempRect.top        = center.y - rotateControlRadius;
  587.         tempRect.right        = center.x + rotateControlRadius;
  588.         tempRect.bottom = center.y + rotateControlRadius;
  589.         workShape = NewOval(&tempRect);
  590.         GXSetShapeFill(workShape, gxClosedFrameFill);
  591.     #ifdef debugging
  592.         GXIgnoreGraphicsNotice(color_already_set);
  593.     #endif
  594.             SetShapeCommonColor(workShape, gxBlack);
  595.     #ifdef debugging
  596.         GXPopGraphicsNotice();
  597.     #endif
  598.     #if useFilledOval
  599.         GXSetShapePen(workShape, ff(2));
  600.         GXPrimitiveShape(workShape);
  601.         GXSetShapeFill(workShape, gxWindingFill);
  602.     #endif
  603.         GXSetPictureParts(controls, 1, 0, 1, &workShape, nil, nil, nil);
  604.         GXDisposeShape(workShape);
  605.  
  606.         /* create the rotate center */
  607.         tempRect.left        = center.x - rotateCenterSize;
  608.         tempRect.top        = center.y - rotateCenterSize;
  609.         tempRect.right        = center.x + rotateCenterSize;
  610.         tempRect.bottom = center.y + rotateCenterSize;
  611.         workShape = NewOval(&tempRect);
  612.         GXSetShapeFill(workShape, gxWindingFill);
  613.     #ifdef debugging
  614.         GXIgnoreGraphicsNotice(color_already_set);
  615.     #endif
  616.             SetShapeCommonColor(workShape, gxBlack);
  617.     #ifdef debugging
  618.         GXPopGraphicsNotice();
  619.     #endif
  620.         GXSetPictureParts(controls, 2, 0, 1, &workShape, nil, nil, nil);
  621.         GXDisposeShape(workShape);
  622.     }
  623.  
  624.     /* reset the savedCenter to the “no center saved” value */
  625.     targetPtr->savedCenter.x = targetPtr->savedCenter.y = gxPositiveInfinity;
  626. }
  627.  
  628. static void TrackShapeControl(shapeControlPtr targetPtr, long command, gxPoint originalClick)
  629. {
  630.     boolean hiddenControls = false;
  631.     boolean rebuildControls = false;
  632.     gxPolar originalPolar;
  633.     gxPoint originalSize;
  634.     gxPoint pivot;
  635.     gxPoint adjust;
  636.     gxMapping oldMatrix;
  637.     gxPoint oldMouse = originalClick;
  638.     fixed polySrc[10];
  639.     fixed polyDst[10];
  640.  
  641.     /* if we are rotating, then calculate the pivot gxPoint for the rotation and save the original angle */
  642.     if( command == commandRotate ) {
  643.         gxShape centerShape;
  644.         gxPoint tempPoint;
  645.  
  646.         GXGetPictureParts(targetPtr->controls, commandMoveRotateCenter, 1, ¢erShape, nil, nil, nil);
  647.         GetShapeTransformedCenter(centerShape, &pivot);
  648.         tempPoint.x = originalClick.x - pivot.x;
  649.         tempPoint.y = originalClick.y - pivot.y;
  650.         PointToPolar(&tempPoint, &originalPolar);
  651.         targetPtr->savedCenter = pivot;
  652.     } else if( command >= commandScale && command <= commandSkew + 3 ) {
  653.         gxRectangle bounds;
  654.  
  655.         /* get the bounds of the entire selection for all the commands that require it */
  656.         GetShapeTransformedBounds(targetPtr->selection, &bounds);
  657.         IfDebug(bounds.right < bounds.left || bounds.bottom < bounds.top, "\pthe selection should always have a bounds here");
  658.  
  659.         /* calculate the originalSize of the selection and set the original scale gxMapping to identity */
  660.         originalSize.x = bounds.right - bounds.left;
  661.         originalSize.y = bounds.bottom - bounds.top;
  662.         ResetMapping(&oldMatrix);
  663.  
  664.         /* calculate the pivot (or anchor) gxPoint for the scale */
  665.         switch( command ) {
  666.             case commandScale + leftTop:
  667.                 pivot.x = bounds.right;
  668.                 pivot.y = bounds.bottom;
  669.                 break;
  670.             case commandScale + rightTop:
  671.                 pivot.x = bounds.left;
  672.                 pivot.y = bounds.bottom;
  673.                 break;
  674.             case commandScale + rightBottom:
  675.                 pivot.x = bounds.left;
  676.                 pivot.y = bounds.top;
  677.                 break;
  678.             case commandScale + leftBottom:
  679.                 pivot.x = bounds.right;
  680.                 pivot.y = bounds.top;
  681.                 break;
  682.             default:
  683.                 polySrc[0] = 4;
  684.                 polySrc[1] = bounds.left;
  685.                 polySrc[2] = bounds.top;
  686.                 polySrc[3] = bounds.right;
  687.                 polySrc[4] = bounds.top;
  688.                 polySrc[5] = bounds.right;
  689.                 polySrc[6] = bounds.bottom;
  690.                 polySrc[7] = bounds.left;
  691.                 polySrc[8] = bounds.bottom;
  692.                 BlockMove(&polySrc[0], &polyDst[0], sizeof(polySrc));
  693.         }
  694.  
  695.         /* this is the amount that we must adjust the mouse position by in order to have the scaling come out correct */
  696.         adjust.x = FixedAbs(originalClick.x - pivot.x) - originalSize.x;
  697.         adjust.y = FixedAbs(originalClick.y - pivot.y) - originalSize.y;
  698.     }
  699.  
  700.     while( StillDown() ) {
  701.         gxPoint newMouse;
  702.  
  703.         GXGetViewPortMouse(0, &newMouse);
  704.         if( newMouse.x != oldMouse.x || newMouse.y != oldMouse.y ) {
  705.             fixed deltaX = newMouse.x - oldMouse.x;
  706.             fixed deltaY = newMouse.y - oldMouse.y;
  707.             gxMapping newMatrix;
  708.             gxRectangle bounds;
  709.             long count;
  710.             gxShape item;
  711.  
  712.             /* if the controls are not hidden yet, then do so now, unless we are moving the rotate center */
  713.             if( command != commandMoveRotateCenter && ! hiddenControls ) {
  714.                 /* set our flag so that we won’t try to hide the controls again */
  715.                 hiddenControls = true;
  716.  
  717.                 /* mark the area covered by the controls as invalid and then temporarily hide the controls picture */
  718.                 InvalidatePicture(targetPtr, targetPtr->controls);
  719.                 GXSetShapeFill(targetPtr->controls, gxNoFill);
  720.                 RedrawShapeControl(targetPtr);
  721.             }
  722.  
  723.             switch( command ) {
  724.  
  725.                 case commandSelect:
  726.                     /*** needs work */
  727.                     break;
  728.  
  729.                 case commandMove:
  730.                     /* move all the items in the selection, marking their old and new bounds as redraw areas */
  731.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  732.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  733.                     do {
  734.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  735.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  736.                         GXMoveShape(item, deltaX,  deltaY);
  737.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  738.                     } while( --count );
  739.  
  740.                     /* move the controls for all the selected items and update the screen to reflect the new position */
  741.                     GXMoveShape(targetPtr->controls, deltaX, deltaY);
  742.                     RedrawShapeControl(targetPtr);
  743.                     break;
  744.  
  745.                 case commandRotate:
  746.                 {    gxPoint tempPoint;
  747.                     gxPolar newPolar;
  748.  
  749.                     tempPoint.x = newMouse.x - pivot.x;
  750.                     tempPoint.y = newMouse.y - pivot.y;
  751.                     PointToPolar(&tempPoint, &newPolar);
  752.  
  753.                     /* rotate all the items in the selection, marking their old and new bounds as redraw areas */
  754.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  755.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  756.                     do {
  757.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  758.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  759.                         GXRotateShape(item, newPolar.angle - originalPolar.angle, pivot.x, pivot.y);
  760.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  761.                     } while( --count );
  762.  
  763.                     /* copy the newPolar into the originalPolar for our next pass through the loop */
  764.                     originalPolar = newPolar;
  765.  
  766.                     /* invalidate the controls for all the selected items and update the screen to reflect the new position */
  767.                     rebuildControls = true;
  768.                     RedrawShapeControl(targetPtr);
  769.                     break;
  770.                 }
  771.  
  772.                 case commandMoveRotateCenter:
  773.                 {    long index;
  774.  
  775.                     /* move the rotate center control part and the rotate control part */
  776.                     for(index = commandRotate; index <= commandMoveRotateCenter; ++index) {
  777.                         gxShape control;
  778.                         gxTransform xform;
  779.                         long pass;
  780.  
  781.                         GXGetPictureParts(targetPtr->controls, index, 1, &control, nil, nil, &xform);
  782.                         /* invalidate the position before the move and the position after the move */
  783.                         for( pass = 0; pass <= 1; ++pass ) {
  784.                             gxShape boundsShape;
  785.                             gxTransform originalXform;
  786.                             gxMapping matrix;
  787.  
  788.                             if( xform ) {
  789.                                 originalXform = GXGetShapeTransform(control);
  790.                                 GXSetShapeTransform(control, xform);
  791.                             }
  792.                             boundsShape = GXNewRectangle(GetShapeTransformedBounds(control, &bounds));
  793.                             if( xform )
  794.                                 GXSetShapeTransform(control, originalXform);
  795.                             GXMapShape(boundsShape, GXGetShapeMapping(targetPtr->controls, &matrix));
  796.                             GXGetShapeBounds(boundsShape, 0L, &bounds);
  797.                             GXDisposeShape(boundsShape);
  798.                             InvalidateRectangle(targetPtr, &bounds);
  799.  
  800.                             /* if we are on our first pass, then move the control */
  801.                             if( pass == 0 )
  802.                                 GXMoveShape(control, deltaX, deltaY);
  803.                         }
  804.                     }
  805.  
  806.                     /* update the screen to reflect the new positions of these controls */
  807.                     RedrawShapeControl(targetPtr);
  808.                     break;
  809.                 }
  810.  
  811.                 /* scale the gxShape around the pivot gxPoint */
  812.                 case commandScale + leftTop:
  813.                 case commandScale + rightTop:
  814.                 case commandScale + rightBottom:
  815.                 case commandScale + leftBottom:
  816.                 {    fixed scaleX, scaleY;
  817.  
  818.                     /* calculate the scale values for the selection */
  819.                     scaleX = FixedDivide(FixedAbs(newMouse.x - pivot.x) - adjust.x, originalSize.x);
  820.                     scaleY = FixedDivide(FixedAbs(newMouse.y - pivot.y) - adjust.y, originalSize.y);
  821.  
  822.                     /* if we are on the other side of the pivot gxPoint from where we started, then negate the scale */
  823.                     if( ((newMouse.x - pivot.x) ^ (originalClick.x - pivot.x)) < 0 )
  824.                         scaleX = -scaleX;
  825.                     if( ((newMouse.y - pivot.y) ^ (originalClick.y - pivot.y)) < 0 )
  826.                         scaleY = -scaleY;
  827.  
  828.                     /* save our new true scale gxMapping and calculate the delta gxMapping to apply to all the shapes */
  829.                     ResetMapping(&newMatrix);
  830.                     ScaleMapping(&newMatrix, scaleX, scaleY, pivot.x, pivot.y);
  831.                     goto mapSelection;
  832.                 }
  833.  
  834.                 case commandSkew + centerTop:
  835.                     polyDst[1] += deltaX;
  836.                     polyDst[2] += deltaY;
  837.                     polyDst[3] = polyDst[1] + originalSize.x;
  838.                     polyDst[4] = polyDst[2];
  839.                     goto skewSelection;
  840.  
  841.                 case commandSkew + centerRight:
  842.                     polyDst[3] += deltaX;
  843.                     polyDst[4] += deltaY;
  844.                     polyDst[5] = polyDst[3];
  845.                     polyDst[6] = polyDst[4] + originalSize.y;
  846.                     goto skewSelection;
  847.  
  848.                 case commandSkew + centerBottom:
  849.                     polyDst[7] += deltaX;
  850.                     polyDst[8] += deltaY;
  851.                     polyDst[5] = polyDst[7] + originalSize.x;
  852.                     polyDst[6] = polyDst[8];
  853.                     goto skewSelection;
  854.  
  855.                 case commandSkew + centerLeft:
  856.                     polyDst[1] += deltaX;
  857.                     polyDst[2] += deltaY;
  858.                     polyDst[7] = polyDst[1];
  859.                     polyDst[8] = polyDst[2] + originalSize.y;
  860.                     /* fall through into “skewSelection” */
  861.  
  862. skewSelection:            PolyToPolyMap((gxPolygon *)&polySrc, (gxPolygon *)&polyDst, &newMatrix);
  863.                     /* fall through into “mapSelection” */
  864.  
  865. mapSelection:        {    gxMapping deltaMatrix, tempMatrix;
  866.  
  867.                     /* calculate the delta gxMapping */
  868.                     CopyToMapping(&tempMatrix, &newMatrix);
  869.                     InvertMapping(&deltaMatrix, &oldMatrix);
  870.                     MapMapping(&deltaMatrix, &tempMatrix);
  871.  
  872.                     /* map all the shapes in the selection (and invalidate them) */
  873.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  874.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  875.                     do {    gxShape itemCopy;
  876.     
  877.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  878.                         itemCopy = GXCopyToShape(nil, item);
  879.                         GXMapShape(itemCopy, &deltaMatrix);
  880.                         GetShapeTransformedBounds(itemCopy, &bounds);
  881.                         GXDisposeShape(itemCopy);
  882.                         if( bounds.right >= bounds.left + minimumObjectSizeH &&
  883.                             bounds.bottom >= bounds.top + minimumObjectSizeV ) {
  884.                                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  885.                                 GXMapShape(item, &deltaMatrix);
  886.                                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  887.                         } else {
  888.                             /* if the object would have disappeared, then keep the objects old full-delta gxMapping */
  889.                             CopyToMapping(&newMatrix, &oldMatrix);
  890.                         }
  891.                     } while( --count );
  892.  
  893.                     /* save the new full-delta gxMapping in our original gxMapping variable */
  894.                     CopyToMapping(&oldMatrix, &newMatrix);
  895.  
  896.                     /* update the screen to reflect changes to the gxShape and invalidate the controls */
  897.                     RedrawShapeControl(targetPtr);
  898.                     rebuildControls = true;
  899.                     break;
  900.                 }
  901.             }
  902.  
  903.             oldMouse = newMouse;
  904.         }
  905.     }
  906.  
  907.     /* if we changed the geometry of the selection, then re-build the controls for it */
  908.     if( rebuildControls )
  909.         BuildSelectionControls(targetPtr);
  910.  
  911.     if( hiddenControls ) {
  912.         GXSetShapeFill(targetPtr->controls, gxEvenOddFill);
  913.         GXDrawShape(targetPtr->controls);
  914.     }
  915. }
  916.  
  917. static void DeselectShape(shapeControlPtr targetPtr, gxShape item)
  918. {
  919.     if( targetPtr->selection ) {
  920.         long count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  921.         gxShape temp;
  922.  
  923.         do {
  924.             GXGetPictureParts(targetPtr->selection, count, 1, &temp, nil, nil, nil);
  925.         } while( temp != item && --count );
  926.  
  927.         /* if we actually found the item in the selection, then de-select it */
  928.         if( count ) {
  929.             /* invalidate the controls for the selection, since we are changing it */
  930.             InvalidatePicture(targetPtr, targetPtr->controls);
  931.  
  932.             /* remove the item from the selection picture and dispose of the picture item was the only selected gxShape */
  933.             GXSetPictureParts(targetPtr->selection, count, 1, 0, nil, nil, nil, nil);
  934.             if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 0 )
  935.                 DisposeShapeAt(&targetPtr->selection);
  936.  
  937.             /* rebuild the controls for the selection */
  938.             BuildSelectionControls(targetPtr);
  939.         }
  940.     }
  941. }
  942.  
  943. static void SelectShape(shapeControlPtr targetPtr, gxShape item, boolean replaceSelection, boolean bringToFront)
  944. {
  945.     /* move the passed item gxShape to the front of the picture if we are supposed to do so */
  946.     if( bringToFront ) {
  947.         long index = GXGetPicture(targetPtr->items, nil, nil, nil, nil);
  948.         gxShape tempShape;
  949.  
  950.         IfDebug(index == 0, "\ptargetPtr->items must always have items");
  951.         do {
  952.             GXGetPictureParts(targetPtr->items, index, 1, &tempShape, nil, nil, nil);
  953.             if( tempShape == item ) {
  954.                 gxRectangle bounds;
  955.  
  956.                 /* add the gxShape to the end first, then remove it so that our references are always valid */
  957.                 GXSetPictureParts(targetPtr->items, 0, 0, 1, &item, nil, nil, nil);
  958.                 GXSetPictureParts(targetPtr->items, index, 1, 0, nil, nil, nil, nil);
  959.                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  960.             }
  961.         } while( --index );
  962.     }
  963.  
  964.     /* if we are replacing the selection, then un-select all the selected shapes */
  965.     if( replaceSelection && targetPtr->selection ) {
  966.         InvalidatePicture(targetPtr, targetPtr->controls);
  967.         DisposeShapeAt(&targetPtr->selection);
  968.         DisposeShapeAt(&targetPtr->controls);
  969.     }
  970.  
  971.     /* if there is an old selection that we are adding to, then invalidate the area for its controls */
  972.     if( targetPtr->controls )
  973.         InvalidatePicture(targetPtr, targetPtr->controls);
  974.  
  975.     if( item == nil ) {
  976.         DisposeShapeAt(&targetPtr->controls);
  977.         return;
  978.     }
  979.  
  980.     if( targetPtr->selection == nil ) {
  981.         targetPtr->selection = GXNewPicture(1, &item, nil, nil, nil);
  982.     } else {
  983.         long count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  984.         long index = 0;
  985.         gxShape selectionItem;
  986.         gxShape mainItem;
  987.  
  988.         /* look through the selection for the first gxShape AFTER item in the main items picture; insert item before this index */
  989.         IfDebug(count == 0, "\pselection must have items in it here");
  990.         do {    long mainIndex = 0;
  991.  
  992.             GXGetPictureParts(targetPtr->selection, ++index, 1, &selectionItem, nil, nil, nil);
  993.             do {
  994.                 IfDebug(mainIndex == GXGetPicture(targetPtr->items, nil, nil, nil, nil),
  995.                     "\pselected item not in main items picture");
  996.                 GXGetPictureParts(targetPtr->items, ++mainIndex, 1, &mainItem, nil, nil, nil);
  997.             } while( mainItem != selectionItem && mainItem != item );
  998.         } while( mainItem == selectionItem && --count );
  999.  
  1000.         /* if we hit the end of the selection without finding a gxShape after item, then set index to zero to insert at the end */
  1001.         if( count == 0 )    index = 0;
  1002.  
  1003.         /* insert our newly selected item in the correct place in the selection */
  1004.         GXSetPictureParts(targetPtr->selection, index, 0, 1, &item, nil, nil, nil);
  1005.     }
  1006.  
  1007.     /* calculate the geometry of the controls for the selection and invalidate this new area */
  1008.     BuildSelectionControls(targetPtr);
  1009.     InvalidatePicture(targetPtr, targetPtr->controls);
  1010. }
  1011.  
  1012.  
  1013. /*----------------------------------------------------------------------------------------------------*/
  1014. /* public routines */
  1015. /*----------------------------------------------------------------------------------------------------*/
  1016.  
  1017.  
  1018. shapeControl NewShapeControl(gxShape items, gxShape background, gxShape foreground)
  1019. {
  1020.     shapeControl target;
  1021.  
  1022.     NilShapeReturnNil(items);
  1023.     IfWarningReturnNil(GXGetShapeType(items) != gxPictureType, picture_expected);
  1024.     IfDebug(firstIndexCommand <= lastSpecialCommand, "\pinvalid gxShape control commands - bug in gxShape control library");
  1025.  
  1026.     if( target = (shapeControl)NewHandleClear(sizeof(shapeControlRecord)) ) {
  1027.         gxViewPort **originalPorts = NewViewPortListFromShape(items);
  1028.         shapeControlPtr targetPtr;
  1029.  
  1030.         HLock((Handle)target);
  1031.         targetPtr = *target;
  1032.  
  1033.         targetPtr->items = GXCloneShape(items);
  1034.  
  1035.         if( background )
  1036.             targetPtr->background = GXCloneShape(background);
  1037.         else {
  1038.             gxColor whiteColor;
  1039.  
  1040.             /* if there is no background, then create a white full gxShape (we always need some sort of background) */
  1041.             targetPtr->background = GXNewShape(gxFullType);
  1042.             whiteColor.space = gxRGBSpace;
  1043.             whiteColor.profile = nil;
  1044.             whiteColor.element.rgb.red = 0xFFFF;
  1045.             whiteColor.element.rgb.green = 0xFFFF;
  1046.             whiteColor.element.rgb.blue = 0xFFFF;
  1047.             GXSetShapeColor(targetPtr->background, &whiteColor);
  1048.         }
  1049.         /* ensure that the background draws to the same ports as the items */
  1050.         DereferenceViewPortList(originalPorts);
  1051.     #ifdef debugging
  1052.         GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  1053.     #endif
  1054.             GXSetShapeViewPorts(targetPtr->background, CountViewPortList(originalPorts), *originalPorts);
  1055.     #ifdef debugging
  1056.         GXPopGraphicsNotice();
  1057.     #endif
  1058.         if( foreground ) {
  1059.             targetPtr->foreground = GXCloneShape(foreground);
  1060.             GXSetShapeViewPorts(targetPtr->foreground, CountViewPortList(originalPorts), *originalPorts);
  1061.         }
  1062.  
  1063.         /* set the savedCenter to the “no center saved” value */
  1064.         targetPtr->savedCenter.x = targetPtr->savedCenter.y = gxPositiveInfinity;
  1065.  
  1066.         ReleaseViewPortList(originalPorts);
  1067.         targetPtr->originalPorts = originalPorts;
  1068.         HUnlock((Handle)target);
  1069.     }
  1070.  
  1071.     return target;
  1072. }
  1073.  
  1074.  
  1075. void DisposeShapeControl(shapeControl target)
  1076. {
  1077.     shapeControlPtr targetPtr;
  1078.  
  1079.     NilParamReturn(target);
  1080.  
  1081.     HLock((Handle)target);
  1082.     targetPtr = *target;
  1083.     GXDisposeShape(targetPtr->items);
  1084.     GXDisposeShape(targetPtr->background);
  1085.     DisposeShapeAt(&targetPtr->foreground);
  1086.     DisposeShapeAt(&targetPtr->selection);
  1087.     DisposeShapeAt(&targetPtr->controls);
  1088.     DisposeViewPortList(targetPtr->originalPorts);
  1089.     HUnlock((Handle)target);
  1090.  
  1091.     /* dispose of the gxShape control memory itself */
  1092.     DisposeHandle((Handle)target);
  1093. }
  1094.  
  1095.  
  1096. gxShape GetShapeControlSelection(const shapeControl source)
  1097. {
  1098.     if (source == nil)                                    // dmh 9-14-93
  1099.     {
  1100.         GXPostGraphicsError(parameter_is_nil);
  1101.         return nil;
  1102.     }
  1103.     
  1104. //    NilParamReturn(source);                                // doesn't return a value.
  1105.  
  1106.     if( (*source)->selection )
  1107.         return GXCopyToShape(nil, (*source)->selection);
  1108.     else
  1109.         return nil;
  1110. }
  1111.  
  1112.  
  1113. void SetShapeControlsSelection(shapeControl target, gxShape shapesToSelect, boolean bringToFront, boolean replaceSelection)
  1114. {
  1115.     shapeControlPtr targetPtr;
  1116.     long count;
  1117.     boolean updateControls = false;
  1118.  
  1119.     NilParamReturn(target);
  1120.     HLock((Handle)target);
  1121.     targetPtr = *target;
  1122.  
  1123.     /* prevent the selection’s controls from getting re-build by the various intermediate steps */
  1124.     ++targetPtr->buildHiddenLevel;
  1125.  
  1126.     /* if we are replacing the old selection, then de-select all the old shapes that will not be in the new selection */
  1127.     if( replaceSelection && targetPtr->selection ) {
  1128.         count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  1129.  
  1130.         do {    /* scan through all the items in the selection, de-selecting all the ones not in shapesToSelect */
  1131.             gxShape item;
  1132.             boolean itemInNewSelection;
  1133.  
  1134.             GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  1135.             itemInNewSelection = false;
  1136.             if( shapesToSelect == nil || (shapesToSelect != item && GXGetShapeType(shapesToSelect) == gxPictureType
  1137.                 && ShapeInPicture(item, shapesToSelect) == false) ) {
  1138.                     updateControls = true;
  1139.                     GXSetPictureParts(targetPtr->selection, count, 1, 0, nil, nil, nil, nil);
  1140.             }
  1141.         } while( --count );
  1142.  
  1143.         if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 0 ) {
  1144.             DisposeShapeAt(&targetPtr->selection);
  1145.             DisposeShapeAt(&targetPtr->controls);
  1146.         }
  1147.     }
  1148.  
  1149.     /* if there is a new selection, then remove any already selected shapes from it */
  1150.     if( shapesToSelect ) {
  1151.         /* is the new selection a non-empty list of items? */
  1152.         if( GXGetShapeType(shapesToSelect) == gxPictureType && (count = GXGetPicture(shapesToSelect, nil, nil, nil, nil)) ) {
  1153.             /*** bring all the new items to the front in the order that they are in shapesToSelect */
  1154.  
  1155.             /* make a copy of the new item list to be selected so that we can munge the list */
  1156.             shapesToSelect = GXCopyToShape(nil, shapesToSelect);
  1157.  
  1158.             do {    /* remove all the already selected items from our new list to select */
  1159.                 gxShape item;
  1160.  
  1161.                 /* if the item in our new selection is already selected, then remove it from our new selection */
  1162.                 GXGetPictureParts(shapesToSelect, count, 1, &item, nil, nil, nil);
  1163.                 if( targetPtr->selection && ShapeInPicture(item, targetPtr->selection) ||
  1164.                     ShapeInPicture(item, targetPtr->items) == false )
  1165.                         GXSetPictureParts(shapesToSelect, count, 1, 0, nil, nil, nil, nil);
  1166.             } while( --count );
  1167.  
  1168.             /* if there are any items left in the new list to be selected, then add them to the current selection */
  1169.             if( count = GXGetPicture(shapesToSelect, nil, nil, nil, nil) ) {
  1170.                 do {    /* note that we don’t bring these items to the front, since we have already done this */
  1171.                     gxShape item;
  1172.  
  1173.                     GXGetPictureParts(shapesToSelect, count, 1, &item, nil, nil, nil);
  1174.                     SelectShape(targetPtr, item, false, false);
  1175.                     updateControls = true;
  1176.                 } while( --count );
  1177.             }
  1178.  
  1179.             /* dispose of our munged copy of the new items that have been selected */
  1180.             GXDisposeShape(shapesToSelect);
  1181.         } else if( ShapeInPicture(shapesToSelect, targetPtr->selection) == false ) {
  1182.             if( ShapeInPicture(shapesToSelect, targetPtr->items) ) {
  1183.                 SelectShape(targetPtr, shapesToSelect, bringToFront, false);
  1184.                 updateControls = true;
  1185.             }
  1186.         }
  1187.     }
  1188.  
  1189.     /* allow the selection’s controls to get re-built and re-build them if they have changed */
  1190.     --targetPtr->buildHiddenLevel;
  1191.     if( updateControls )
  1192.         BuildSelectionControls(targetPtr);
  1193.  
  1194.     HUnlock((Handle)target);
  1195. }
  1196.  
  1197.  
  1198. gxShape GetShapeControlSelectionHandles(const shapeControl source)
  1199. {
  1200.     if (source == nil)                                    // dmh 9-14-93
  1201.     {
  1202.         GXPostGraphicsError(parameter_is_nil);
  1203.         return nil;
  1204.     }
  1205.     
  1206. //    NilParamReturn(source);                                // doesn't return a value.
  1207.  
  1208.     if( (*source)->controls )
  1209.         return GXCopyDeepToShape(nil, (*source)->controls);
  1210.     else
  1211.         return nil;
  1212. }
  1213.  
  1214.  
  1215. boolean SendEventToShapeControl(shapeControl target, EventRecord *event)
  1216. {
  1217.     boolean handledEvent = false;
  1218.  
  1219.     if (target == nil || event == nil)                    // dmh 9-14-93
  1220.     {
  1221.         GXPostGraphicsError(parameter_is_nil);
  1222.         return handledEvent;
  1223.     }
  1224.  
  1225. //    NilParamReturn(target);                                // doesn't return a value.
  1226. //    NilParamReturn(event);                                // doesn't return a value.
  1227.  
  1228.     HLock((Handle)target);
  1229.     switch( event->what ) {
  1230.         case keyDown:
  1231.             break;    /*** change this to handle the key and return true if it is ours */
  1232.         case mouseDown:
  1233.         {    gxPoint originalClick;
  1234.             gxHitTestInfo result;
  1235.             gxShape itemShape;
  1236.             long command;
  1237.             shapeControlPtr targetPtr = *target;
  1238.  
  1239.             {    gxViewPort originalPort = *DereferenceViewPortList(targetPtr->originalPorts);
  1240.                 GXConvertQDPoint(&event->where, originalPort, &originalClick);
  1241.             }
  1242.  
  1243.             /* if there is a selection, then see if any of the selection’s controls have been hit and track them if they have */
  1244.             if( targetPtr->selection ) {
  1245.                 if( GXHitTestPicture(targetPtr->controls, &originalClick, &result, 1, 1) != gxNoPart ) {
  1246.                     TrackShapeControl(targetPtr, result.containerIndex, originalClick);
  1247.                     handledEvent = true;
  1248.                     break;
  1249.                 }
  1250.             }
  1251.  
  1252.             /* see if an item gxShape was hit; if so then add it to the selection and track it */
  1253.             if( GXHitTestPicture(targetPtr->items, &originalClick, &result, 1, 1) != gxNoPart ) {
  1254.                 command = commandMove;
  1255.                 itemShape = result.which;
  1256.  
  1257.                 if( targetPtr->selection && ShapeInPicture(itemShape, targetPtr->selection) ) {
  1258.                     if( event->modifiers & shiftKey ) {
  1259.                         /* the gxShape was already selected and we shift-clicked it, so deselect it and return */
  1260.                         DeselectShape(targetPtr, itemShape);
  1261.                         RedrawShapeControl(targetPtr);
  1262.                         handledEvent = true;
  1263.                         break;
  1264.                     }
  1265.                 } else {
  1266.                     /* select the new gxShape - the last two parameters are (boolean replaceSelection, bringToFront) */
  1267.                     SelectShape(targetPtr, itemShape, (event->modifiers & shiftKey) == 0,
  1268.                         (event->modifiers & optionKey) != 0);
  1269.                     RedrawShapeControl(targetPtr);
  1270.                 }
  1271.  
  1272.                 /* allow the user to move the entire selection around */
  1273.                 TrackShapeControl(targetPtr, commandMove, originalClick);
  1274.                 handledEvent = true;
  1275.                 break;
  1276.             }
  1277.  
  1278.             /* see if we are within the bounds of any of the original viewPorts of the items */
  1279.             {    gxShape testShape = GXNewPoint(&originalClick);
  1280.                 gxViewPort *portList = DereferenceViewPortList(targetPtr->originalPorts);
  1281.  
  1282.             #ifdef debugging
  1283.                 GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  1284.             #endif
  1285.                     GXSetShapeViewPorts(testShape, CountViewPortList(targetPtr->originalPorts), portList);
  1286.             #ifdef debugging
  1287.                 GXPopGraphicsNotice();
  1288.             #endif
  1289.                 /* scan through all the original viewPorts of the items */
  1290.                 while( *portList ) {
  1291.                     /* if a gxPoint at the original click location would draw to any devices in this port, then we are
  1292.                     within the bounds of the port. we should now drag out a gray gxRectangle to select all the items
  1293.                     contained within it */
  1294.                     if( GXGetShapeGlobalViewDevices(testShape, *portList, nil) ) {
  1295.                         GXDisposeShape(testShape);
  1296.  
  1297.                     #if 0     /*** needs work */
  1298.                         TrackShapeControl(targetPtr, commandSelect, originalClick);
  1299.                     #else
  1300.                         /* for now, just de-select all the items */
  1301.                         SelectShape(targetPtr, nil, true, false);
  1302.                     #endif
  1303.  
  1304.                         RedrawShapeControl(targetPtr);
  1305.                         ReleaseViewPortList(targetPtr->originalPorts);
  1306.                         HUnlock((Handle)target);
  1307.                         return true;
  1308.                     }
  1309.                     ++portList;
  1310.                 }
  1311.                 GXDisposeShape(testShape);
  1312.                 ReleaseViewPortList(targetPtr->originalPorts);
  1313.             }
  1314.  
  1315.             /* nothing was hit, so break and return that we didn’t handle the event */
  1316.             break;
  1317.         }
  1318.     }
  1319.  
  1320.     HUnlock((Handle)target);
  1321.     return handledEvent;
  1322. }
  1323.  
  1324.  
  1325. void InvalidateShapeControlShape(shapeControl target, gxShape invalidShape)
  1326. {
  1327.     NilParamReturn(target);
  1328.     NilShapeReturn(invalidShape);
  1329.  
  1330.     HLock((Handle)target);
  1331.     InvalidatePicture(*target, invalidShape);
  1332.     HUnlock((Handle)target);
  1333. }
  1334.  
  1335.  
  1336. void InvalidateShapeControlRectangle(shapeControl target, gxRectangle *bounds)
  1337. {
  1338.     NilParamReturn(target);
  1339.     NilParamReturn(bounds);
  1340.  
  1341.     HLock((Handle)target);
  1342.     InvalidateRectangle(*target, bounds);
  1343.     HUnlock((Handle)target);
  1344. }
  1345.  
  1346.  
  1347. void UpdateShapeControl(shapeControl target)
  1348. {
  1349.     NilParamReturn(target);
  1350.     HLock((Handle)target);
  1351.     RedrawShapeControl(*target);
  1352.     HUnlock((Handle)target);
  1353. }
  1354.